#include "AliveSender.h"
#include "P2PChat.h"
#include "User.h"

//total timeout is 30 + random(0, 10) seconds
#define BASE_TIMEOUT 30
#define CONN_PORT 54303
#define TTL 32

/**
 * Constructor
 */
AliveSender::AliveSender() 
{
	port = CONN_PORT;
	ttl = TTL;
	
	/* Init the udp socket (BSD) */
	sock = socket(AF_INET, SOCK_DGRAM, 0);
	
	if(sock == -1)
	{
		//TODO emit critical signal
		int errsv = errno;
		qWarning() << "Alive Sender: socket descriptor invalid!" << "errno:" << strerror(errsv);
	}
	
	/** 
	 * TTL settings
	 */
	multicastAddr.sin_family = AF_INET;
	multicastAddr.sin_port = htons(port);
	multicastAddr.sin_addr.s_addr = inet_addr("238.38.38.38");
	
	if(setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL,
			(const char *)&ttl, sizeof(ttl)) < 0)
	{
		int errsv = errno;
		//TODO emit critical signal
		qWarning() << "Alive Sender: setsocket option failed! (TTL)" << "errno:" << strerror(errsv);
	}
	
	/**
	 * MEMBERSHIP settings
	 */			
	memset(&multicastReq, 0, sizeof(struct ip_mreq));
	multicastReq.imr_multiaddr.s_addr = inet_addr("238.38.38.38");
	multicastReq.imr_interface.s_addr = htons(INADDR_ANY);
	
	if(setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
			(const char *)&multicastReq, sizeof(multicastReq)) < 0)
	{
		int errsv = errno;
		//TODO emit critical signal
		qWarning() << "Alive Sender: setsocket option failed! (MEMBERSHIP)" << "errno:" << strerror(errsv);
	}
	
	checkDisconnectedClients = false;
	
	/* init random seed */
	qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
	
	/* init timer */
	timer = new QTimer(this);
	timer->setInterval((BASE_TIMEOUT + (qrand() %10)) * 1000);
	
	connect(timer, SIGNAL(timeout()), this, SLOT(sendAliveMsgOnTimeout()));		
}

/**
 * SLOT:
 * Send the alive multicast message on timeout
 */
void AliveSender::sendAliveMsgOnTimeout()
{
	qDebug() << "Timeout: sending my alive message to multicast group";
	
	/* Send the alive datagram */
	QByteArray aliveMessage = 
		P2PChat::getInstance()->getCommands()->value("alive")->getCommandDatagram();
	
	if(
		sendto(	sock,
				aliveMessage,
				aliveMessage.size(),
				0,
				(struct sockaddr *)&multicastAddr,
				sizeof(multicastAddr)) == -1)
	{
		
		//TODO warning alive send failed!
		qWarning() << "warning: alive send failed!";
	}

	/* We need to checkDisconnectedClients? */ 
	if(checkDisconnectedClients)
	{
		QMutexLocker locker(P2PChat::getInstance()->getUserMutex());	//LOCK
		
		QMapIterator<QString, User*> it(*(P2PChat::getInstance()->getUsers()));
		QStringList listIDtoDelete;
		
		while(it.hasNext())
		{
			it.next();
			qDebug() << it.key() << *(it.value()->getNick()) << "(" << it.value()->getLastSeen() << ")"
			<< (signed int)(it.value()->getLastSeen() + (timer->interval() / 1000) - QDateTime::currentDateTime().toTime_t());
			
			//If the client had skipped the last alive update, maybe it's disconnected 
			if(	(signed int)
						(it.value()->getLastSeen() + 
						(timer->interval() / 1000) - 
						QDateTime::currentDateTime().toTime_t()) < 0)
			{
				//this client in off-line (presumably). delete it
				listIDtoDelete << it.key();				
			}	
		}
		
		qDebug() << "off-line:" << listIDtoDelete;
		
		//Delete offline users if exists 
		if(!listIDtoDelete.empty())
		{
			QStringListIterator iter(listIDtoDelete);
			while(iter.hasNext())
			{
				User *toNotify = P2PChat::getInstance()->getUsers()->take(iter.next());
				
				//notify the client disconnession by a signal
				emit userDisconnected(toNotify);
				
			}
		}
		
		checkDisconnectedClients = false;
	}
	else
	{
		//check disconnected clients at the next timeout
		checkDisconnectedClients = true;
	}
}

/**
 * Get timer object
 * 
 * @return QTimer*
 */
QTimer* AliveSender::getTimer()
{
	return timer;
}
